// Copyright 2013-2022 The btcsuite developers

package musig2

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"sync"
	"testing"

	"github.com/btcsuite/btcd/btcec/v2"
)

const (
	testVectorBaseDir = "data"
)

func mustParseHex(str string) []byte {
	b, err := hex.DecodeString(str)
	if err != nil {
		panic(fmt.Errorf("unable to parse hex: %v", err))
	}

	return b
}

type signer struct {
	privKey *btcec.PrivateKey
	pubKey  *btcec.PublicKey

	nonces *Nonces

	partialSig *PartialSignature
}

type signerSet []signer

func (s signerSet) keys() []*btcec.PublicKey {
	keys := make([]*btcec.PublicKey, len(s))
	for i := 0; i < len(s); i++ {
		keys[i] = s[i].pubKey
	}

	return keys
}

func (s signerSet) partialSigs() []*PartialSignature {
	sigs := make([]*PartialSignature, len(s))
	for i := 0; i < len(s); i++ {
		sigs[i] = s[i].partialSig
	}

	return sigs
}

func (s signerSet) pubNonces() [][PubNonceSize]byte {
	nonces := make([][PubNonceSize]byte, len(s))
	for i := 0; i < len(s); i++ {
		nonces[i] = s[i].nonces.PubNonce
	}

	return nonces
}

func (s signerSet) combinedKey() *btcec.PublicKey {
	uniqueKeyIndex := secondUniqueKeyIndex(s.keys(), false)
	key, _, _, _ := AggregateKeys(
		s.keys(), false, WithUniqueKeyIndex(uniqueKeyIndex),
	)
	return key.FinalKey
}

// testMultiPartySign executes a multi-party signing context w/ 100 signers.
func testMultiPartySign(t *testing.T, taprootTweak []byte,
	tweaks ...KeyTweakDesc) {

	const numSigners = 100

	// First generate the set of signers along with their public keys.
	signerKeys := make([]*btcec.PrivateKey, numSigners)
	signSet := make([]*btcec.PublicKey, numSigners)
	for i := 0; i < numSigners; i++ {
		privKey, err := btcec.NewPrivateKey()
		if err != nil {
			t.Fatalf("unable to gen priv key: %v", err)
		}

		pubKey := privKey.PubKey()

		signerKeys[i] = privKey
		signSet[i] = pubKey
	}

	var combinedKey *btcec.PublicKey

	var ctxOpts []ContextOption
	switch {
	case len(taprootTweak) == 0:
		ctxOpts = append(ctxOpts, WithBip86TweakCtx())
	case taprootTweak != nil:
		ctxOpts = append(ctxOpts, WithTaprootTweakCtx(taprootTweak))
	case len(tweaks) != 0:
		ctxOpts = append(ctxOpts, WithTweakedContext(tweaks...))
	}

	ctxOpts = append(ctxOpts, WithKnownSigners(signSet))

	// Now that we have all the signers, we'll make a new context, then
	// generate a new session for each of them(which handles nonce
	// generation).
	signers := make([]*Session, numSigners)
	for i, signerKey := range signerKeys {
		signCtx, err := NewContext(
			signerKey, false, ctxOpts...,
		)
		if err != nil {
			t.Fatalf("unable to generate context: %v", err)
		}

		if combinedKey == nil {
			combinedKey, err = signCtx.CombinedKey()
			if err != nil {
				t.Fatalf("combined key not available: %v", err)
			}
		}

		session, err := signCtx.NewSession()
		if err != nil {
			t.Fatalf("unable to generate new session: %v", err)
		}
		signers[i] = session
	}

	// Next, in the pre-signing phase, we'll send all the nonces to each
	// signer.
	var wg sync.WaitGroup
	for i, signCtx := range signers {
		signCtx := signCtx

		wg.Add(1)
		go func(idx int, signer *Session) {
			defer wg.Done()

			for j, otherCtx := range signers {
				if idx == j {
					continue
				}

				nonce := otherCtx.PublicNonce()
				haveAll, err := signer.RegisterPubNonce(nonce)
				if err != nil {
					t.Fatalf("unable to add public nonce")
				}

				if j == len(signers)-1 && !haveAll {
					t.Fatalf("all public nonces should have been detected")
				}
			}
		}(i, signCtx)
	}

	wg.Wait()

	msg := sha256.Sum256([]byte("let's get taprooty"))

	// In the final step, we'll use the first signer as our combiner, and
	// generate a signature for each signer, and then accumulate that with
	// the combiner.
	combiner := signers[0]
	for i := range signers {
		signer := signers[i]
		partialSig, err := signer.Sign(msg)
		if err != nil {
			t.Fatalf("unable to generate partial sig: %v", err)
		}

		// We don't need to combine the signature for the very first
		// signer, as it already has that partial signature.
		if i != 0 {
			haveAll, err := combiner.CombineSig(partialSig)
			if err != nil {
				t.Fatalf("unable to combine sigs: %v", err)
			}

			if i == len(signers)-1 && !haveAll {
				t.Fatalf("final sig wasn't reconstructed")
			}
		}
	}

	// Finally we'll combined all the nonces, and ensure that it validates
	// as a single schnorr signature.
	finalSig := combiner.FinalSig()
	if !finalSig.Verify(msg[:], combinedKey) {
		t.Fatalf("final sig is invalid!")
	}

	// Verify that if we try to sign again with any of the existing
	// signers, then we'll get an error as the nonces have already been
	// used.
	for _, signer := range signers {
		_, err := signer.Sign(msg)
		if err != ErrSigningContextReuse {
			t.Fatalf("expected to get signing context reuse")
		}
	}
}

// TestMuSigMultiParty tests that for a given set of 100 signers, we're able to
// properly generate valid sub signatures, which ultimately can be combined
// into a single valid signature.
func TestMuSigMultiParty(t *testing.T) {
	t.Parallel()

	testTweak := [32]byte{
		0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF,
		0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D,
		0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
		0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
	}

	t.Run("no_tweak", func(t *testing.T) {
		t.Parallel()

		testMultiPartySign(t, nil)
	})

	t.Run("tweaked", func(t *testing.T) {
		t.Parallel()

		testMultiPartySign(t, nil, KeyTweakDesc{
			Tweak: testTweak,
		})
	})

	t.Run("tweaked_x_only", func(t *testing.T) {
		t.Parallel()

		testMultiPartySign(t, nil, KeyTweakDesc{
			Tweak:   testTweak,
			IsXOnly: true,
		})
	})

	t.Run("taproot_tweaked_x_only", func(t *testing.T) {
		t.Parallel()

		testMultiPartySign(t, testTweak[:])
	})

	t.Run("taproot_bip_86", func(t *testing.T) {
		t.Parallel()

		testMultiPartySign(t, []byte{})
	})
}

// TestMuSigEarlyNonce tests that for protocols where nonces need to be
// exchanged before all signers are known, the context API works as expected.
func TestMuSigEarlyNonce(t *testing.T) {
	t.Parallel()

	privKey1, err := btcec.NewPrivateKey()
	if err != nil {
		t.Fatalf("unable to gen priv key: %v", err)
	}
	privKey2, err := btcec.NewPrivateKey()
	if err != nil {
		t.Fatalf("unable to gen priv key: %v", err)
	}

	// If we try to make a context, with just the private key and sorting
	// value, we should get an error.
	_, err = NewContext(privKey1, true)
	if !errors.Is(err, ErrSignersNotSpecified) {
		t.Fatalf("unexpected ctx error: %v", err)
	}

	signers := []*btcec.PublicKey{privKey1.PubKey(), privKey2.PubKey()}
	numSigners := len(signers)

	ctx1, err := NewContext(
		privKey1, true, WithNumSigners(numSigners), WithEarlyNonceGen(),
	)
	if err != nil {
		t.Fatalf("unable to make ctx: %v", err)
	}
	pubKey1 := ctx1.PubKey()

	ctx2, err := NewContext(
		privKey2, true, WithKnownSigners(signers), WithEarlyNonceGen(),
	)
	if err != nil {
		t.Fatalf("unable to make ctx: %v", err)
	}
	pubKey2 := ctx2.PubKey()

	// At this point, the combined key shouldn't be available for signer 1,
	// but should be for signer 2, as they know about all signers.
	if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
		t.Fatalf("unepxected error: %v", err)
	}
	_, err = ctx2.CombinedKey()
	if err != nil {
		t.Fatalf("unable to get combined key: %v", err)
	}

	// The early nonces _should_ be available at this point.
	nonce1, err := ctx1.EarlySessionNonce()
	if err != nil {
		t.Fatalf("session nonce not available: %v", err)
	}
	nonce2, err := ctx2.EarlySessionNonce()
	if err != nil {
		t.Fatalf("session nonce not available: %v", err)
	}

	// The number of registered signers should still be 1 for both parties.
	if ctx1.NumRegisteredSigners() != 1 {
		t.Fatalf("expected 1 signer, instead have: %v",
			ctx1.NumRegisteredSigners())
	}
	if ctx2.NumRegisteredSigners() != 2 {
		t.Fatalf("expected 2 signers, instead have: %v",
			ctx2.NumRegisteredSigners())
	}

	// If we try to make a session, we should get an error since we dn't
	// have all the signers yet.
	if _, err := ctx1.NewSession(); !errors.Is(err, ErrNotEnoughSigners) {
		t.Fatalf("unexpected session key error: %v", err)
	}

	// The combined key should also be unavailable as well.
	if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
		t.Fatalf("unexpected combined key error: %v", err)
	}

	// We'll now register the other signer for party 1.
	done, err := ctx1.RegisterSigner(&pubKey2)
	if err != nil {
		t.Fatalf("unable to register signer: %v", err)
	}
	if !done {
		t.Fatalf("signer 1 doesn't have all keys")
	}

	// If we try to register the signer again, we should get an error.
	_, err = ctx2.RegisterSigner(&pubKey1)
	if !errors.Is(err, ErrAlreadyHaveAllSigners) {
		t.Fatalf("should not be able to register too many signers")
	}

	// We should be able to create the session at this point.
	session1, err := ctx1.NewSession()
	if err != nil {
		t.Fatalf("unable to create new session: %v", err)
	}
	session2, err := ctx2.NewSession()
	if err != nil {
		t.Fatalf("unable to create new session: %v", err)
	}

	msg := sha256.Sum256([]byte("let's get taprooty, LN style"))

	// If we try to sign before we have the combined nonce, we should get
	// an error.
	_, err = session1.Sign(msg)
	if !errors.Is(err, ErrCombinedNonceUnavailable) {
		t.Fatalf("unable to gen sig: %v", err)
	}

	// Now we can exchange nonces to continue with the rest of the signing
	// process as normal.
	done, err = session1.RegisterPubNonce(nonce2.PubNonce)
	if err != nil {
		t.Fatalf("unable to register nonce: %v", err)
	}
	if !done {
		t.Fatalf("signer 1 doesn't have all nonces")
	}
	done, err = session2.RegisterPubNonce(nonce1.PubNonce)
	if err != nil {
		t.Fatalf("unable to register nonce: %v", err)
	}
	if !done {
		t.Fatalf("signer 2 doesn't have all nonces")
	}

	// Registering the nonce again should error out.
	_, err = session2.RegisterPubNonce(nonce1.PubNonce)
	if !errors.Is(err, ErrAlredyHaveAllNonces) {
		t.Fatalf("shouldn't be able to register nonces twice")
	}

	// Sign the message and combine the two partial sigs into one.
	_, err = session1.Sign(msg)
	if err != nil {
		t.Fatalf("unable to gen sig: %v", err)
	}
	sig2, err := session2.Sign(msg)
	if err != nil {
		t.Fatalf("unable to gen sig: %v", err)
	}
	done, err = session1.CombineSig(sig2)
	if err != nil {
		t.Fatalf("unable to combine sig: %v", err)
	}
	if !done {
		t.Fatalf("all sigs should be known now: %v", err)
	}

	// If we try to combine another sig, then we should get an error.
	_, err = session1.CombineSig(sig2)
	if !errors.Is(err, ErrAlredyHaveAllSigs) {
		t.Fatalf("shouldn't be able to combine again")
	}

	// Finally, verify that the final signature is valid.
	combinedKey, err := ctx1.CombinedKey()
	if err != nil {
		t.Fatalf("unexpected combined key error: %v", err)
	}
	finalSig := session1.FinalSig()
	if !finalSig.Verify(msg[:], combinedKey) {
		t.Fatalf("final sig is invalid!")
	}
}

type memsetRandReader struct {
	i int
}

func (mr *memsetRandReader) Read(buf []byte) (n int, err error) {
	for i := range buf {
		buf[i] = byte(mr.i)
	}
	return len(buf), nil
}

// TestSigningWithAggregatedNonce tests the aggregated nonce signing flow where
// nonces are aggregated externally and provided to participants via
// RegisterCombinedNonce, rather than each participant aggregating nonces
// themselves via RegisterPubNonce.
func TestSigningWithAggregatedNonce(t *testing.T) {
	t.Run("basic flow", func(t *testing.T) {
		const numSigners = 5

		// Generate signers.
		signerKeys := make([]*btcec.PrivateKey, numSigners)
		signSet := make([]*btcec.PublicKey, numSigners)
		for i := 0; i < numSigners; i++ {
			privKey, err := btcec.NewPrivateKey()
			if err != nil {
				t.Fatalf("unable to gen priv key: %v", err)
			}
			signerKeys[i] = privKey
			signSet[i] = privKey.PubKey()
		}

		// Each signer creates a context and session.
		sessions := make([]*Session, numSigners)
		for i, signerKey := range signerKeys {
			signCtx, err := NewContext(
				signerKey, false, WithKnownSigners(signSet),
			)
			if err != nil {
				t.Fatalf("unable to generate context: %v", err)
			}

			session, err := signCtx.NewSession()
			if err != nil {
				t.Fatalf("unable to generate new session: %v", err)
			}
			sessions[i] = session
		}

		// Phase 1: Collect all public nonces.
		pubNonces := make([][PubNonceSize]byte, numSigners)
		for i, session := range sessions {
			pubNonces[i] = session.PublicNonce()
		}

		// Phase 2: Aggregate nonces externally.
		combinedNonce, err := AggregateNonces(pubNonces)
		if err != nil {
			t.Fatalf("unable to aggregate nonces: %v", err)
		}

		// Phase 3: Participants register combined nonce and sign.
		msg := sha256.Sum256([]byte("aggregated nonce signing"))

		partialSigs := make([]*PartialSignature, numSigners)
		for i, session := range sessions {
			err = session.RegisterCombinedNonce(combinedNonce)
			if err != nil {
				t.Fatalf("signer %d unable to register combined nonce: %v",
					i, err)
			}
			sig, err := session.Sign(msg)
			if err != nil {
				t.Fatalf("signer %d unable to sign: %v", i, err)
			}
			partialSigs[i] = sig
		}

		// Phase 4: Combine all partial signatures.
		finalSig := CombineSigs(partialSigs[0].R, partialSigs)

		// Verify the final signature.
		combinedKey, _, _, err := AggregateKeys(signSet, false)
		if err != nil {
			t.Fatalf("unable to aggregate keys: %v", err)
		}

		if !finalSig.Verify(msg[:], combinedKey.FinalKey) {
			t.Fatalf("final signature is invalid")
		}
	})

	t.Run("error: register combined nonce twice", func(t *testing.T) {
		privKey, _ := btcec.NewPrivateKey()
		privKey2, _ := btcec.NewPrivateKey()
		signSet := []*btcec.PublicKey{privKey.PubKey(), privKey2.PubKey()}

		signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
		session, _ := signCtx.NewSession()

		fakeCombinedNonce := getValidNonce(t)

		// First call should succeed.
		err := session.RegisterCombinedNonce(fakeCombinedNonce)
		if err != nil {
			t.Fatalf("first RegisterCombinedNonce failed: %v", err)
		}

		// Second call should fail.
		err = session.RegisterCombinedNonce(fakeCombinedNonce)
		if err != ErrAlredyHaveAllNonces {
			t.Fatalf("expected ErrAlredyHaveAllNonces, got: %v", err)
		}
	})

	t.Run("error: register combined nonce after register pub nonce",
		func(t *testing.T) {

			privKey, _ := btcec.NewPrivateKey()
			privKey2, _ := btcec.NewPrivateKey()
			privKey3, _ := btcec.NewPrivateKey()
			signSet := []*btcec.PublicKey{
				privKey.PubKey(),
				privKey2.PubKey(),
				privKey3.PubKey(),
			}

			signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
			session, _ := signCtx.NewSession()

			signCtx2, _ := NewContext(privKey2, false, WithKnownSigners(signSet))
			session2, _ := signCtx2.NewSession()

			// Register one public nonce first.
			_, err := session.RegisterPubNonce(session2.PublicNonce())
			if err != nil {
				t.Fatalf("RegisterPubNonce failed: %v", err)
			}

			// Now try to register a combined nonce - this should fail.
			fakeCombinedNonce := [PubNonceSize]byte{}
			err = session.RegisterCombinedNonce(fakeCombinedNonce)
			if err == nil {
				t.Fatalf("expected error when calling RegisterCombinedNonce " +
					"after RegisterPubNonce")
			}
		})

	t.Run("error: register pub nonce after register combined nonce",
		func(t *testing.T) {

			const numSigners = 3

			signerKeys := make([]*btcec.PrivateKey, numSigners)
			signSet := make([]*btcec.PublicKey, numSigners)
			for i := 0; i < numSigners; i++ {
				privKey, _ := btcec.NewPrivateKey()
				signerKeys[i] = privKey
				signSet[i] = privKey.PubKey()
			}

			sessions := make([]*Session, numSigners)
			for i, signerKey := range signerKeys {
				signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
				session, _ := signCtx.NewSession()
				sessions[i] = session
			}

			pubNonces := make([][PubNonceSize]byte, numSigners)
			for i, session := range sessions {
				pubNonces[i] = session.PublicNonce()
			}

			combinedNonce, _ := AggregateNonces(pubNonces)

			// Register the combined nonce first.
			err := sessions[0].RegisterCombinedNonce(combinedNonce)
			if err != nil {
				t.Fatalf("RegisterCombinedNonce failed: %v", err)
			}

			// Now try to register individual nonces - this should fail.
			_, err = sessions[0].RegisterPubNonce(pubNonces[1])
			if err == nil {
				t.Fatalf("expected error when calling RegisterPubNonce " +
					"after RegisterCombinedNonce")
			}
		})

	t.Run("nonce reuse prevention", func(t *testing.T) {
		privKey, _ := btcec.NewPrivateKey()
		privKey2, _ := btcec.NewPrivateKey()
		signSet := []*btcec.PublicKey{privKey.PubKey(), privKey2.PubKey()}

		signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
		session, _ := signCtx.NewSession()

		fakeCombinedNonce := getValidNonce(t)
		session.RegisterCombinedNonce(fakeCombinedNonce)

		msg := sha256.Sum256([]byte("nonce reuse test"))

		// First sign should succeed.
		_, err := session.Sign(msg)
		if err != nil {
			t.Fatalf("first sign failed: %v", err)
		}

		// Second sign should fail due to nonce reuse.
		_, err = session.Sign(msg)
		if err != ErrSigningContextReuse {
			t.Fatalf("expected nonce reuse error, got: %v", err)
		}
	})

	t.Run("incorrect combined nonce produces invalid sig", func(t *testing.T) {
		const numSigners = 3

		signerKeys := make([]*btcec.PrivateKey, numSigners)
		signSet := make([]*btcec.PublicKey, numSigners)
		for i := 0; i < numSigners; i++ {
			privKey, _ := btcec.NewPrivateKey()
			signerKeys[i] = privKey
			signSet[i] = privKey.PubKey()
		}

		sessions := make([]*Session, numSigners)
		for i, signerKey := range signerKeys {
			signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
			session, _ := signCtx.NewSession()
			sessions[i] = session
		}

		pubNonces := make([][PubNonceSize]byte, numSigners)
		for i, session := range sessions {
			pubNonces[i] = session.PublicNonce()
		}

		// Create INCORRECT combined nonce using only a subset.
		wrongNonces := pubNonces[:2]
		incorrectCombinedNonce, _ := AggregateNonces(wrongNonces)

		msg := sha256.Sum256([]byte("incorrect nonce test"))

		partialSigs := make([]*PartialSignature, numSigners)
		for i, session := range sessions {
			session.RegisterCombinedNonce(incorrectCombinedNonce)
			sig, _ := session.Sign(msg)
			partialSigs[i] = sig
		}

		finalSig := CombineSigs(partialSigs[0].R, partialSigs)
		combinedKey, _, _, _ := AggregateKeys(signSet, false)

		// Final signature should be INVALID.
		if finalSig.Verify(msg[:], combinedKey.FinalKey) {
			t.Fatalf("final signature should be invalid with incorrect nonce")
		}
	})

	t.Run("mixed registration methods", func(t *testing.T) {
		const numSigners = 4

		signerKeys := make([]*btcec.PrivateKey, numSigners)
		signSet := make([]*btcec.PublicKey, numSigners)
		for i := 0; i < numSigners; i++ {
			privKey, _ := btcec.NewPrivateKey()
			signerKeys[i] = privKey
			signSet[i] = privKey.PubKey()
		}

		sessions := make([]*Session, numSigners)
		for i, signerKey := range signerKeys {
			signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
			session, _ := signCtx.NewSession()
			sessions[i] = session
		}

		pubNonces := make([][PubNonceSize]byte, numSigners)
		for i, session := range sessions {
			pubNonces[i] = session.PublicNonce()
		}

		combinedNonce, _ := AggregateNonces(pubNonces)
		msg := sha256.Sum256([]byte("mixed registration test"))

		// Half use RegisterCombinedNonce.
		for i := 0; i < numSigners/2; i++ {
			sessions[i].RegisterCombinedNonce(combinedNonce)
		}

		// Other half use RegisterPubNonce.
		for i := numSigners / 2; i < numSigners; i++ {
			for j, nonce := range pubNonces {
				if i == j {
					continue
				}
				sessions[i].RegisterPubNonce(nonce)
			}
		}

		// All should be able to sign.
		partialSigs := make([]*PartialSignature, numSigners)
		for i, session := range sessions {
			sig, err := session.Sign(msg)
			if err != nil {
				t.Fatalf("signer %d unable to sign: %v", i, err)
			}
			partialSigs[i] = sig
		}

		finalSig := CombineSigs(partialSigs[0].R, partialSigs)
		combinedKey, _, _, _ := AggregateKeys(signSet, false)

		if !finalSig.Verify(msg[:], combinedKey.FinalKey) {
			t.Fatalf("final signature is invalid")
		}
	})

	t.Run("get combined nonce after RegisterCombinedNonce", func(t *testing.T) {
		privKey, _ := btcec.NewPrivateKey()
		privKey2, _ := btcec.NewPrivateKey()
		signSet := []*btcec.PublicKey{privKey.PubKey(), privKey2.PubKey()}

		signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
		session, _ := signCtx.NewSession()

		// Should fail before registering combined nonce.
		_, err := session.CombinedNonce()
		if err != ErrCombinedNonceUnavailable {
			t.Fatalf("expected ErrCombinedNonceUnavailable, got: %v", err)
		}

		// Register combined nonce.
		expectedNonce := getValidNonce(t)
		err = session.RegisterCombinedNonce(expectedNonce)
		if err != nil {
			t.Fatalf("RegisterCombinedNonce failed: %v", err)
		}

		// Should succeed after registering.
		gotNonce, err := session.CombinedNonce()
		if err != nil {
			t.Fatalf("CombinedNonce failed: %v", err)
		}

		if gotNonce != expectedNonce {
			t.Fatalf("expected nonce %x, got %x", expectedNonce, gotNonce)
		}
	})

	t.Run("get combined nonce after RegisterPubNonce", func(t *testing.T) {
		const numSigners = 3

		signerKeys := make([]*btcec.PrivateKey, numSigners)
		signSet := make([]*btcec.PublicKey, numSigners)
		for i := 0; i < numSigners; i++ {
			privKey, _ := btcec.NewPrivateKey()
			signerKeys[i] = privKey
			signSet[i] = privKey.PubKey()
		}

		sessions := make([]*Session, numSigners)
		for i, signerKey := range signerKeys {
			signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
			session, _ := signCtx.NewSession()
			sessions[i] = session
		}

		pubNonces := make([][PubNonceSize]byte, numSigners)
		for i, session := range sessions {
			pubNonces[i] = session.PublicNonce()
		}

		// Should fail before all nonces are registered.
		_, err := sessions[0].CombinedNonce()
		if err != ErrCombinedNonceUnavailable {
			t.Fatalf("expected ErrCombinedNonceUnavailable before all nonces, got: %v", err)
		}

		// Register all nonces via RegisterPubNonce.
		for i := 1; i < numSigners; i++ {
			sessions[0].RegisterPubNonce(pubNonces[i])
		}

		// Should succeed after all nonces are registered.
		gotNonce, err := sessions[0].CombinedNonce()
		if err != nil {
			t.Fatalf("CombinedNonce failed: %v", err)
		}

		// Verify it matches what AggregateNonces produces.
		expectedNonce, _ := AggregateNonces(pubNonces)
		if gotNonce != expectedNonce {
			t.Fatalf("combined nonce mismatch: expected %x, got %x",
				expectedNonce[:8], gotNonce[:8])
		}
	})
}

func getValidNonce(t *testing.T) [PubNonceSize]byte {
	t.Helper()

	var nonce [PubNonceSize]byte

	privKey, err := btcec.NewPrivateKey()
	if err != nil {
		t.Fatalf("unable to gen priv key: %v", err)
	}
	copy(nonce[:33], privKey.PubKey().SerializeCompressed())
	copy(nonce[33:], privKey.PubKey().SerializeCompressed())

	return nonce
}
